package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

import java.io.EOFException;

import java.util.Vector;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.HashMap;

/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see	nachos.vm.VMProcess
 * @see	nachos.network.NetProcess
 */
public class UserProcess {
    /**
     * Allocate a new process.
     */
    public UserProcess() {
	int numPhysPages = Machine.processor().getNumPhysPages();
	pageTable = new TranslationEntry[numPhysPages];
	for (int i=0; i<numPhysPages; i++)
	    pageTable[i] = new TranslationEntry(i,i, true,false,false,false);

	// added by Tom
	OpenFile stdin, stdout;
	files = new Vector<OpenFile>( artificialLimit, 16 );
	stdin = UserKernel.console.openForReading();
	stdout = UserKernel.console.openForWriting();
	files.add( stdin );
	files.add( stdout );
	nextAvailFile = new LinkedList<Integer>();
	for ( int i = 2; i < artificialLimit; ++i )
	    {
		nextAvailFile.add( i );
		files.add( null ); // add the empty null elements so we can directly access any index 
	    }

	// Tom: this is partially a hack - there is no limit and loop around 
	// like linux and unix have. 20 - pidooma method
	pid = usedpids;
	++usedpids;
	if ( usedpids < 0 )
	    {
		// overflow
		Lib.debug(dbgProcess, "Warning: usedpids has overflowed.");
	    }
	procs = new HashMap<Integer, UserProcess>( 20 );

	myExecRef = null;
    }
    
    /**
     * Allocate and return a new process of the correct class. The class name
     * is specified by the <tt>nachos.conf</tt> key
     * <tt>Kernel.processClassName</tt>.
     *
     * @return	a new process of the correct class.
     */
    public static UserProcess newUserProcess() {
	System.out.println("Machine.getProcessClassName() = <" + Machine.getProcessClassName() + ">" );
	return (UserProcess)Lib.constructObject(Machine.getProcessClassName());
    }

    /**
     * Execute the specified program with the specified arguments. Attempts to
     * load the program, and then forks a thread to run it.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the program was successfully executed.
     */
    public boolean execute(String name, String[] args) {
	if (!load(name, args))
	    return false;
	
	myThread = new UThread(this);
	myThread.setName(name);
	myThread.fork();

	return true;
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * Called by <tt>UThread.saveState()</tt>.
     */
    public void saveState() {
    }

    /**
     * Restore the state of this process after a context switch. Called by
     * <tt>UThread.restoreState()</tt>.
     */
    public void restoreState() {
	//	System.err.println("Restoring state of " + this);

	Machine.processor().setPageTable(pageTable);
    }

    /**
     * Read a null-terminated string from this process's virtual memory. Read
     * at most <tt>maxLength + 1</tt> bytes from the specified address, search
     * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
     * without including the null terminator. If no null terminator is found,
     * returns <tt>null</tt>.
     *
     * @param	vaddr	the starting virtual address of the null-terminated
     *			string.
     * @param	maxLength	the maximum number of characters in the string,
     *				not including the null terminator.
     * @return	the string read, or <tt>null</tt> if no null terminator was
     *		found.
     */
    public String readVirtualMemoryString(int vaddr, int maxLength) {
	Lib.assertTrue(maxLength >= 0);

	byte[] bytes = new byte[maxLength+1];

	int bytesRead = readVirtualMemory(vaddr, bytes);

	for (int length=0; length<bytesRead; length++) {
	    if (bytes[length] == 0)
		return new String(bytes, 0, length);
	}

	return null;
    }

    /**
     * Transfer data from this process's virtual memory to all of the specified
     * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data) {
	return readVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from this process's virtual memory to the specified array.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @param	offset	the first byte to write in the array.
     * @param	length	the number of bytes to transfer from virtual memory to
     *			the array.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data, int offset,
				 int length) {
	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

	byte[] memory = Machine.processor().getMemory();
	
	if (vaddr < 0 || vaddr >= memory.length)
	    {
		// seg fault - terminate 
		terminateProcess( -1 );
		return -1;
	    }
       
	int pageno, pgoff, ppn;
	pageno = Processor.pageFromAddress( vaddr );
	pgoff  = Processor.offsetFromAddress( vaddr );
	
	//Lib.debug( dbgProcess, "pageno = " + pageno + "\tpgoff = " + pgoff );
	
	int amount = Math.min(length, memory.length-vaddr);
	int realaddr = Processor.makeAddress( pageTable[pageno].ppn, pgoff );
	//Lib.debug( dbgProcess, "reading from address = " + realaddr + "\tvaddr was " + vaddr + " and reading = " + amount );
	//System.arraycopy(memory, vaddr, data, offset, amount);
	System.arraycopy(memory, realaddr, data, offset, amount);
	
	return amount;
    }

    /**
     * Transfer all data from the specified array to this process's virtual
     * memory.
     * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data) {
	return writeVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from the specified array to this process's virtual memory.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @param	offset	the first byte to transfer from the array.
     * @param	length	the number of bytes to transfer from the array to
     *			virtual memory.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data, int offset,
				  int length) {
	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

	byte[] memory = Machine.processor().getMemory();

	if (vaddr < 0 || vaddr >= memory.length)
	    {
		// seg fault - terminate 
		terminateProcess( -1 );
		return -1;
	    }

	int pageno, pgoff, ppn;
	pageno = Processor.pageFromAddress( vaddr );
	pgoff  = Processor.offsetFromAddress( vaddr );
	
	//Lib.debug( dbgProcess, "pageno = " + pageno + "\tpgoff = " + pgoff );
	
	if ( pageTable[pageno].readOnly == true )
	    {
		// seg fault - terminate 
		terminateProcess( -1 );
		return -1; // this is an error - segfault
	    }
	
	int amount = Math.min(length, memory.length-vaddr);

	int realaddr = Processor.makeAddress( pageTable[pageno].ppn,  pgoff );
	//Lib.debug( dbgProcess, "writing to address = " + realaddr + "\tvaddr was " + vaddr );

	//System.arraycopy(data, offset, memory, vaddr, amount);
	System.arraycopy(data, offset, memory, realaddr, amount);

	pageTable[pageno].dirty = true; // mark that we have written to it, this isn't doing anything because we aren't swapping pages. 

	return amount;
    }

    /**
     * Load the executable with the specified name into this process, and
     * prepare to pass it the specified arguments. Opens the executable, reads
     * its header information, and copies sections and arguments into this
     * process's virtual memory.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the executable was successfully loaded.
     */
    private boolean load(String name, String[] args) {
	Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");
	
	OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
	if (executable == null) {
	    Lib.debug(dbgProcess, "\topen failed");
	    return false;
	}

	try {
	    coff = new Coff(executable);
	}
	catch (EOFException e) {
	    executable.close();
	    Lib.debug(dbgProcess, "\tcoff load failed");
	    return false;
	}

	// make sure the sections are contiguous and start at page 0
	// Tom note: everything is contiguous in virtual memory but not in 
	// physical memory. 
	numPages = 0;
	for (int s=0; s<coff.getNumSections(); s++) {
	    CoffSection section = coff.getSection(s);
	    if (section.getFirstVPN() != numPages) {
		coff.close();
		Lib.debug(dbgProcess, "\tfragmented executable");
		return false;
	    }
	    numPages += section.getLength();
	}

	// make sure the argv array will fit in one page
	byte[][] argv = new byte[args.length][];
	int argsSize = 0;
	for (int i=0; i<args.length; i++) {
	    argv[i] = args[i].getBytes();
	    // 4 bytes for argv[] pointer; then string plus one for null byte
	    argsSize += 4 + argv[i].length + 1;
	}
	if (argsSize > pageSize) {
	    coff.close();
	    Lib.debug(dbgProcess, "\targuments too long");
	    return false;
	}

	// program counter initially points at the program entry point
	initialPC = coff.getEntryPoint();	

	// next comes the stack; stack pointer initially points to top of it
	numPages += stackPages;
	initialSP = numPages*pageSize;

	// and finally reserve 1 page for arguments
	numPages++;

	if (!loadSections())
	    return false;

	// store arguments in last page
	int entryOffset = (numPages-1)*pageSize;
	int stringOffset = entryOffset + args.length*4;

	this.argc = args.length;
	this.argv = entryOffset;
	
	for (int i=0; i<argv.length; i++) {
	    byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
	    Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4);
	    entryOffset += 4;
	    Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) ==
		       argv[i].length);
	    stringOffset += argv[i].length;
	    Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1);
	    stringOffset += 1;
	}


	// Tom - need to save the executable file so it can be cleaned up
	myExecRef = executable;

	return true;
    }

    /**
     * Allocates memory for this process, and loads the COFF sections into
     * memory. If this returns successfully, the process will definitely be
     * run (this is the last step in process initialization that can fail).
     *
     * @return	<tt>true</tt> if the sections were successfully loaded.
     */
    protected boolean loadSections() {
	if (numPages > Machine.processor().getNumPhysPages()) {
	    coff.close();
	    Lib.debug(dbgProcess, "\tinsufficient physical memory");
	    return false;
	}

	int i, svpn;
	svpn = 0;
	// load sections
	for (int s=0; s<coff.getNumSections(); s++) {
	    CoffSection section = coff.getSection(s);
	    
	    Lib.debug(dbgProcess, "\tinitializing " + section.getName()
		      + " section (" + section.getLength() + " pages)");

	    for (i=0; i<section.getLength(); i++) {
		int vpn = section.getFirstVPN()+i;
		int ppn;

		// for now, just assume virtual addresses=physical addresses
		Lib.debug( dbgProcess, "\tloading section " + i + " at vpn = " + vpn );

		
		ppn = UserKernel.getNextPage();

		if ( ppn == -1 )
		    {
			Lib.debug( dbgProcess, "\tOut of Memory " );
			// need to kick in unload sections and terminate
			unloadSections();
			return false;
		    }

		pageTable[vpn].vpn      = vpn;
		pageTable[vpn].ppn      = ppn;
		pageTable[vpn].valid    = true; 
		pageTable[vpn].used     = true;
		pageTable[vpn].dirty    = false;
		pageTable[vpn].readOnly = section.isReadOnly();

		Lib.debug( dbgProcess, "\tget Free page was " + ppn );
		
		//section.loadPage(i, vpn);
		section.loadPage(i, ppn);

		// count the pages loaded
		++svpn;
	    }
	}
	
	// need to allocate remaining pages
	// use svpn to see how many were already allocated
	for ( i = svpn; i < numPages; ++i )
	    {
		int ppn;

		ppn = UserKernel.getNextPage();

		if ( ppn == -1 )
		    {
			Lib.debug( dbgProcess, "\tOut of Memory (2)" );
			// need to kick in unload sections and terminate
			unloadSections();
			return false;			
		    }

		pageTable[i].vpn      = i;
		pageTable[i].ppn      = ppn;
		pageTable[i].valid    = true;
		pageTable[i].used     = true;
		pageTable[i].dirty    = false;
		pageTable[i].readOnly = false;
	    }
		
	return true;
    }

    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    protected void unloadSections() {

	// loop throught the page table and remove used pages
	// and free them within the kernel.
	for ( int i = 0; i < pageTable.length; ++i )
	    {
		if ( pageTable[i].valid && pageTable[i].used )
		    {
			UserKernel.freePage( pageTable[i].ppn );
			
			pageTable[i].valid = false;
			pageTable[i].used  = false;
			pageTable[i].ppn   = -1;
		    }
	    }
    }    

    /**
     * Initialize the processor's registers in preparation for running the
     * program loaded into this process. Set the PC register to point at the
     * start function, set the stack pointer register to point at the top of
     * the stack, set the A0 and A1 registers to argc and argv, respectively,
     * and initialize all other registers to 0.
     */
    public void initRegisters() {
	Processor processor = Machine.processor();

	// by default, everything's 0
	for (int i=0; i<processor.numUserRegisters; i++)
	    processor.writeRegister(i, 0);

	// initialize PC and SP according
	processor.writeRegister(Processor.regPC, initialPC);
	processor.writeRegister(Processor.regSP, initialSP);

	// initialize the first two argument registers to argc and argv
	processor.writeRegister(Processor.regA0, argc);
	processor.writeRegister(Processor.regA1, argv);
    }

    /**
     * Handle the halt() system call. 
     */
    private int handleHalt() {

	// if root process allow to proceed, otherwise return 0;
	if ( pid != 0 )
	    return 0;
	
	Machine.halt();
	
	Lib.assertNotReached("Machine.halt() did not halt machine!");
	return 0;
    }
    
    private int handleExit( int status )
    {
	if ( pid == 0 ) // root processing ending
	    Kernel.kernel.terminate();

	terminateProcess( status );
	
	return 0;
    }
    
    private int handleExec( int vmaddr_name, int argc, int vmaddr_argv )
    {
	String filename;
	String[] args;
	int cpid;
	
	// check virtual addresses
	if ( !validVirtualAddress( vmaddr_name ) || !validVirtualAddress( vmaddr_argv ) )
	    terminateProcess(-1); //return -1;
	
	if ( argc < 1 )
	    return -1;
	
	// get the filename
	filename = readVirtualMemoryString( vmaddr_name, MAXSTRSIZE );
	
	// get the arguments
	args = new String[ argc ];
	for ( int i = 0; i < argc; ++i )
	    {
		byte[] bytes = new byte[4];
		readVirtualMemory( vmaddr_argv+i*4, bytes);
		int vmaddr_str = Lib.bytesToInt( bytes, 0 );
		//Lib.debug('z', "vm addr is " + vmaddr_str);
		args[i] = readVirtualMemoryString( vmaddr_str, MAXSTRSIZE );
				
		Lib.debug('z', "handleExec - args[" + i + "] = " + args[i]);
	    }
	
	UserProcess child;
	//	child = new UserProcess();

	// tom - this doesn't work after 15 loads, security manager
	// gets mad 
	// 
	// Mike - We need to use this (or something like it) for
	// project 3 because we need child processes to also be
	// instances of VMProcess. If it still blows up after 15 loads
	// we'll have to come up with something else.
	child = UserProcess.newUserProcess(); 
	
	cpid = child.pid;
	
	procs.put( cpid, child );
	Lib.debug('z', "handleExec - filename is " + filename + " and args.length = " + args.length );
	if ( !child.execute( filename, args ) )
	    return -1;
	
	return cpid;
    }

    private int handleJoin( int pid, int vmaddr_status )
    {
	int stat;

	// check validity of vmaddr_status
	if ( !validVirtualAddress( vmaddr_status ) )
	    terminateProcess( -1 );//return 0;
	
	// get the process	
	UserProcess proc = procs.get( pid );
	
	if ( proc == null )
	    return -1;
	
	if ( proc.myThread == null )
	    return -1;
	
	proc.myThread.join();
	stat = proc.myExitStatus;

	// access the memory and store the status
	byte[] data = new byte[4];
	data = Lib.bytesFromInt( stat );
	writeVirtualMemory( vmaddr_status, data, 0, 4 ); // assuming int is 4 bytes

	if ( stat == -1 )
	    return 0;

	return 1;
    }

    // merge these two system calls together - only difference is that one will create the file
    private int handleCreatOpen( int vmaddr, boolean create )
    {
	int ret;
	
	byte[] bytes;
	int fd = -1;
	OpenFile openfile;

	// is this a valid vmaddr???
	if ( !validVirtualAddress( vmaddr ) )
	    terminateProcess( -1 ); //return -1;

	// locate file, open it and add to open files	
	String filename = readVirtualMemoryString( vmaddr, MAXSTRSIZE);
	openfile = UserKernel.fileSystem.open( filename,  create );
	
	if ( openfile == null )
	    return -1;
	
	fd = nextAvailFile.remove();
	files.set( fd, openfile );

	return fd;
    }

    private int handleRead( int fd, int vmaddr, int count )
    {
	OpenFile reader;
	byte[]   bytes;
	int      ret;

	// is fd valid
	try
	    {
		reader = files.get( fd );
		
		if ( reader == null )
		    return -1;
	    }
	catch (Exception e)
	    {
		Lib.debug('s', "reader exception debug trace\n" + e.toString() );
		return -1;		
	    }

	// is vmaddr valid and within range
	if ( !validVirtualAddress( vmaddr ) )
	    terminateProcess( -1 ); //return -1;

	// is count > 0 
	if ( count < 0 )
	    return -1;
	if ( count == 0 )
	    return 0;

	bytes = new byte[count];
	       
	ret = reader.read( bytes, 0, count );
	if ( ret < 0 )
	    return -1;

	if ( ret == 0 )
	    return 0;

	ret = writeVirtualMemory( vmaddr, bytes, 0, count );
	
	return ret;	
    }

    private int handleWrite( int fd, int vmaddr, int size )
    {
	OpenFile writer;
	byte[] bytes;
	int ret;

	// valid size
	if ( size < 0 )
	    return -1;

	// check virtual address validity
	if ( !validVirtualAddress( vmaddr ) )
	    terminateProcess( -1 ); //return -1;	

	// valid file descriptor
	try
	    {
		writer = files.get( fd );

		if ( writer == null )
		    return -1;
	    }
	catch (Exception e)
	    {
		Lib.debug('s', "writer exception debug trace\n" + e.toString() );
		return -1;
	    }

	
	// first get the buffer
	bytes = new byte[ size ];	
	ret = readVirtualMemory( vmaddr, bytes, 0, size );
	if ( ret == -1 )
	    {
		Lib.debug('s', "Error in handleWrite from readVirtualMemory!"); // temporary
		return -1;
	    }
	ret = writer.write( bytes, 0, size );

	if ( ret == 0 )
	    Lib.debug('s', "ret is 0!");

	return ret;
    }

    private int handleClose( int fd )
    {
	OpenFile openfile;

	// is fd valid
	try 
	    {
		openfile = files.get(fd);

		if ( openfile == null )
		    return -1;
	    }
	catch (Exception e)
	    {
		Lib.debug('s', "handleClose exception debug trace\n" + e.toString() );
		return -1;
	    }

	openfile.close();
	files.set(fd, null );
	if ( fd > 1 ) // don't allow 0 or 1 to be reused if closed
	    nextAvailFile.add( fd );

	return 0;
    }

    private int handleUnlink( int vmaddr )
    {
	String filename;

	// check vmaddr
	// check virtual address validity
	if ( !validVirtualAddress( vmaddr ) )
	    terminateProcess( -1 ); //return -1;	

	// this assumes the filesystem is smart enough to check open files
	filename = readVirtualMemoryString( vmaddr, MAXSTRSIZE );
	if ( UserKernel.fileSystem.remove( filename ) )
	    return 0;	
	
	return -1;
    }

    private static final int
        syscallHalt = 0,
	syscallExit = 1,
	syscallExec = 2,
	syscallJoin = 3,
	syscallCreate = 4,
	syscallOpen = 5,
	syscallRead = 6,
	syscallWrite = 7,
	syscallClose = 8,
	syscallUnlink = 9;

    /**
     * Handle a syscall exception. Called by <tt>handleException()</tt>. The
     * <i>syscall</i> argument identifies which syscall the user executed:
     *
     * <table>
     * <tr><td>syscall#</td><td>syscall prototype</td></tr>
     * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
     * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
     * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
     * 								</tt></td></tr>
     * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
     * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
     * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
     * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
     * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
     * </table>
     * 
     * @param	syscall	the syscall number.
     * @param	a0	the first syscall argument.
     * @param	a1	the second syscall argument.
     * @param	a2	the third syscall argument.
     * @param	a3	the fourth syscall argument.
     * @return	the value to be returned to the user.
     */
    public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {

	switch (syscall) {
	case syscallHalt:
	    return handleHalt();
	case syscallExit:	    
	    return handleExit( a0 );
	case syscallExec:
	    return handleExec( a0, a1, a2 );
	case syscallJoin:	    
	    return handleJoin( a0, a1);
	case syscallCreate:
	    return handleCreatOpen( a0, true );
	case syscallOpen:
	    return handleCreatOpen( a0, false );
	case syscallRead:
	    return handleRead( a0, a1, a2 );
	case syscallWrite:
	    return handleWrite( a0, a1, a2 );
	case syscallClose:
	    return handleClose( a0 );
	case syscallUnlink:
	    return handleUnlink( a0 );
	default:
	    Lib.debug(dbgProcess, "Unknown syscall " + syscall);
	    Lib.assertNotReached("Unknown system call!");
	}
	return 0;
    }

    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    public void handleException(int cause) {
	Processor processor = Machine.processor();

	switch (cause) {
	case Processor.exceptionSyscall:
	    int result = handleSyscall(processor.readRegister(Processor.regV0),
				       processor.readRegister(Processor.regA0),
				       processor.readRegister(Processor.regA1),
				       processor.readRegister(Processor.regA2),
				       processor.readRegister(Processor.regA3)
				       );
	    processor.writeRegister(Processor.regV0, result);
	    processor.advancePC();
	    break;				       
				       
	default:
	    Lib.debug(dbgProcess, "Unexpected exception: " +
		      Processor.exceptionNames[cause]);
	    Lib.assertNotReached("Unexpected exception");
	}
    }

    /** The program being run by this process. */
    protected Coff coff;

    /** This process's page table. */
    protected TranslationEntry[] pageTable;
    /** The number of contiguous pages occupied by the program. */
    protected int numPages;

    /** The number of pages in the program's stack. */
    protected final int stackPages = 8;
    
    private int initialPC, initialSP;
    private int argc, argv;
    
    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';

    // new code
    protected OpenFile             myExecRef;
    protected Vector<OpenFile>     files;
    protected LinkedList<Integer>  nextAvailFile;
    protected final int            artificialLimit = 32;
    protected static int           MAXSTRSIZE = 256;
    // support for multiprogramming
    protected int pid;
    protected static int usedpids = 0;
    protected HashMap<Integer, UserProcess> procs;
    protected UThread myThread;
    protected int myExitStatus;

    // TODO: Make me private again?
    protected boolean validVirtualAddress( int vmaddr )
    {
	if ( vmaddr < 0 || vmaddr >= numPages*pageSize )
	    return false;
	return true;
    }

    // TODO: Make me private again?
    protected void terminateProcess( int status )
    {
	unloadSections();

	// clean up the files (in case the user forgets!)
	Iterator iter = files.iterator();
	
	while ( iter.hasNext() )
	    {
		OpenFile oftemp = (OpenFile)iter.next();

		if ( oftemp != null ) // need this check since we keep the null elements
		    oftemp.close();
	    }
	files = null;

	// clean up the executable - otherwise the StubFileSystem screws you over
	if ( myExecRef != null )
	    myExecRef.close();

	myExecRef = null;

	myExitStatus = status;
	myThread.finish();
    }

    public String toString() {
	String name = myThread == null ? "" : myThread.getName();
	return super.toString() + " (" + name + ")";
    }
}

